/*
 * @file_name: uri_impl.ipp
 * @date: 2021/12/06
 * @author: oaho
 * Copyright @ hz oaho, All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#ifndef HTTP_URI_IMPL_IPP
#define HTTP_URI_IMPL_IPP
#include <string>
#include <cstring>
#include <map>
#include <http/uri.hpp>
namespace http{
  namespace detail{

    class uri_exception: public std::exception{
    public:
      using native_handle_type = size_t;
    public:
      uri_exception(native_handle_type index, const char* msg){
        this->msg = "[uri_exception] index ";
        this->msg += std::to_string(index) + ", ";
        this->msg += msg;
      }
      virtual const char* what() const noexcept override{
        return msg.data();
      }
      virtual ~uri_exception(){}
    private:
      native_handle_type index;
      std::string msg;
    };


    struct impl{
    public:
      impl():_port(80){}
      impl(const impl&) = default;
      impl(const std::string& path){
        initial(path);
      }

      impl(impl&& other){
        _path = std::move(other._path);
        _host = std::move(other._host);
        _port = other._port;other._port = 80;
        query = std::move(other.query);
      }
      ~impl(){
      }

      impl& operator = (const impl& _other){
        _host = _other._host;
        _path = _other._path;
        _port = _other._port;
        query = _other.query;
        return *this;
      }

      const std::string& host()const{
        return _host;
      }

      void host(const std::string& _host){
        this->_host = _host;
        parse_host_internal(this->_host);
      }

      template<size_t N> const void host(const char (&arr)[N]){
        this->_host.template assign(arr, N);
        parse_host_internal(_host);
      }

      const unsigned short port() const{
        return _port;
      }

      const std::string& path() const{
        return _path;
      }

      void path(const std::string& _path){
        this->_path = _path;
        reset();
        initial_l2(_path, 0);
      }

      template<size_t N> void path(const char (&arr)[N]){
        _path.assign(arr, N);
        reset();
        initial_l2(_path, 0);
      }

      const std::string& get_parameter(const std::string& key) const{
        static std::string null_value;
        auto it = query.find(key);
        if(it != query.end())return it->second;
        return null_value;
      }

      template<typename T> void set_parameter(const std::string& key, T&& v){
        query[key] = std::move(std::to_string(v));
      }

    private:
      void initial(const string& data){
        this->_port = 80;
        if(data.size() > 7 && !strncmp(data.data(), "http://", 7))
          return initial_lh(data, 7);
        return initial_l2(data, 0);
      }
      void reset(){
        query.clear();
        _host.clear();
        _path.clear();
        _port = 80;
      }
      //parse have host
      void initial_lh(const string& data, size_t offset){
        auto host_end = data.find('/', offset);
        if(host_end == std::string::npos){
          _host = std::move(data.substr(offset));
        }
        else _host = std::move(data.substr(offset, host_end - offset));
        //parse host and port
        parse_host_internal(_host);
        //parse path
        initial_l2(data, host_end);
      }
      void initial_l2(const string& data, size_t offset){
        if( data.size() - offset < 2)return;
        if(data[offset] != '/')throw uri_exception(offset, "the first character must be '/'");
        auto path_pos = data.find('?', offset);
        if( path_pos == std::string::npos){
          this->_path = std::move(data.substr(offset));
          return;
        }
        this->_path = data.substr(offset, path_pos - offset);
        parse_parameter(data, path_pos + 1);
      }

      void parse_host_internal(std::string& host){
        auto dot_pos = host.find(':');
        if(dot_pos == std::string::npos)return;
        int port = std::stoi(host.substr(dot_pos + 1));
        if(this->_port > 65535)
          throw uri_exception(dot_pos + 1, "the port > 65535");
        host.erase(dot_pos);
        this->_port = (unsigned short)port;
      }
      //xxx=yyy
      void parse_parameter(const std::string& data, size_t offset){
        auto split_ = data.find('&', offset);
        if(split_ == std::string::npos){
          if(offset >= data.size())return;
          return parse_equal(data, split_, offset);
        }
        parse_equal(data, split_, offset);
        parse_parameter(data, split_ + 1);
      }
      void parse_equal(const std::string& data, size_t split, size_t offset){
        auto equal = data.find('=', offset);
        if(equal == std::string::npos || equal > split)return;
        query[data.substr(offset, equal - offset)] = data.substr(equal + 1, split - equal - 1);
      }
    private:
      std::string _host;
      unsigned short _port;
      std::string _path;
      std::map<std::string, std::string> query;
    };
};};
#endif // VIDEO_ON_DEMAND_URI_IMPL_IPP
